'How do I keep the value of a checkbox in an invalid form in this Laravel 8 application?

I am working on a Laravel 8 blogging application. The "Add article" form has a "switch" (checkbox) that lets the user choose whether or not the post will be a featured one.

The form:

<form method="POST" action="{{ route('dashboard.articles.add') }}" enctype="multipart/form-data" novalidate>
        @csrf

        <div class="col-md-12 @error('title') has-error @enderror">
            <input id="title" type="text" placeholder="Title" class="form-control @error('title') is-invalid @enderror" name="title" value="{{ old('title') }}" autocomplete="title" autofocus>

            @error('title')
                    <span class="invalid-feedback" role="alert">
                            <strong>{{ $message }}</strong>
                    </span>
            @enderror
    </div>

    <div class="row mb-2">
        <label for="short_description" class="col-md-12">{{ __('Short description') }}</label>

        <div class="col-md-12 @error('short_description') has-error @enderror">
                <input id="short_description" type="text" placeholder="Short description" class="form-control @error('short_description') is-invalid @enderror" name="short_description" value="{{ old('short_description') }}" autocomplete="short_description" autofocus>

                @error('short_description')
                        <span class="invalid-feedback" role="alert">
                                <strong>{{ $message }}</strong>
                        </span>
                @enderror
        </div>
    </div>

    <div class="row mb-2">
            <label for="category" class="col-md-12">{{ __('Category') }}</label>
        
            <div class="col-md-12 @error('category_id') has-error @enderror">
        
                <select name="category_id" id="category" class="form-control @error('category_id') is-invalid @enderror">
                    <option value="0">Pick a category</option>
                    @foreach($categories as $category)
                        <option value="{{ $category->id }}">{{ $category->name }}</option>
                    @endforeach
                </select>
        
                    @error('category_id')
                            <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                            </span>
                    @enderror
            </div>
    </div>

    <div class="row mb-2">
        <div class="col-md-12 d-flex align-items-center switch-toggle">
                <p class="mb-0 me-3">Featured article?</p>
                <input id="featured" class="mt-1" type="checkbox" name="featured">
                <label class="px-1" for="featured">{{ __('Toggle') }}</label>
        </div>
    </div>

    <button type="submit" class="w-100 btn btn-primary">{{ __('Save') }}</button>

</form>

The controller:

class ArticleController extends Controller
{
    
    private $rules = [
        'category_id' => 'required|exists:article_categories,id',
        'title' => 'required|string|max:190',
        'short_description' => 'required|string|max:190',
    ];

    private $messages = [
        'category_id.required' => 'Please pick a category for the article',
        'title.required' => 'Please provide a title for the article',
        'short_description.required' => 'The article needs a short description',
        'short_description.max' => 'The short description field is too long',
    ];
    
    public function categories() {
        return ArticleCategory::all();
    }
    

    public function create() {
        // Load the view and populate the form with categories
        return view('dashboard/add-article',
            ['categories' => $this->categories()]
        );
    }

    public function save(Request $request) {
        // Validate form (with custom messages)
        $validator = Validator::make($request->all(), $this->rules, $this->messages);

        if ($validator->fails()) {
            return redirect()->back()->withErrors($validator->errors())->withInput();
        }

        $fields = $validator->validated();

        // Turn the 'featured' field value into a tiny integer
        $fields['featured'] = $request->get('featured') == 'on' ? 1 : 0;


        // Data to be added
        $form_data = [
            'user_id' => Auth::user()->id,
            'category_id' => $fields['category_id'],
            'title' => $fields['title'],
            'slug' => Str::slug($fields['title'], '-'),
            'short_description' => $fields['short_description'],
            'featured' => $fields['featured'],
        ];

        // Insert data in the 'articles' table
        $query = Article::create($form_data);

        if ($query) {
            return redirect()->route('dashboard.articles')->with('success', 'The article titled "' . $form_data['title'] . '" was added');
        } else {
            return redirect()->back()->with('error', 'Adding article failed');
        }
    }
}

The problem

When an attempt is made to submit the invalid form, the fields that are valid keep their values.

The unintended exception to the rule is the checkbox <input id="featured" class="mt-1" type="checkbox" name="featured">

What is my mistake?


Solution 1:[1]

  1. Your input has no value, so you can only check on existence not on value in controller ($request->get('featured') will never have the value on)
  2. You need to use old('featured') on that field too
  3. You can skip the check on the featured field in the controller if you set the values to 0 and 1 in the form
    <div class="row mb-2">
        <div class="col-md-12 d-flex align-items-center switch-toggle">
                <p class="mb-0 me-3">Featured article?</p>
                <input type="hidden" value="0" name="featured">
                <input {{ old('featured') ? 'checked':'' }} id="featured" value="1" class="mt-1" type="checkbox" name="featured">
                <label class="px-1" for="featured">{{ __('Toggle') }}</label>
        </div>
    </div>

The hidden field is to send the value 0 if the checkbox is not checked. A checkbox input is not sent in the request if it is not checked.

When checked, the input value will be overwritten to 1.

In the controller you can directly use the input

        $fields = $validator->validated();

        // Data to be added
        $form_data = [
            'user_id' => Auth::user()->id,
            'category_id' => $fields['category_id'],
            'title' => $fields['title'],
            'slug' => Str::slug($fields['title'], '-'),
            'short_description' => $fields['short_description'],
            'featured' => $fields['featured'],
        ];

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 N69S