I wanted to make a method when the page loads, the value will be set by the first option on the dropdown while using a blank state. However, what I did was onChange events where it only changes value passing states to another function. Is there an event where the page loads, it sets to that selected value?
Parent.js
const Parent = () => {
const [choice, setChoice] = useState(""); // <-- Leaving a blank state
return (
<div>
<h1>{choice === "" ? "NO VALUE" : choice}</h1> // <-- Displays the default value of 'NO VALUE' when the page loads, otherwise when selecting an option, a value displays
<Child name={(choice) => setChoice(choice)} />
</div>
);
};
export default Parent;
Child.js
const Child = ({ name }) => {
const [filterChoice, setFilterChoice] = useState("");
useEffect(() => {
console.log("Child: " + filterChoice); // <-- Only displays as 'Child: ' and no value whenever it loads the page, otherwise it displays the value selected
}, [filterChoice]);
const setValue = (e) => {
setFilterChoice(e.target.value);
name(e.target.value);
};
return (
<div>
<select onChange={(e) => setValue(e)} defaultValue="A">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
<option value="E">E</option>
</select>
</div>
);
};
export default Child;
import React, { useEffect, useState } from "react";
const Child = ({ name }) => {
const defaultValue = "A";
const [filterChoice, setFilterChoice] = useState(defaultValue);
useEffect(() => {
name(defaultValue);
console.log("Child: " + filterChoice); // <-- Only displays as 'Child: ' and no value whenever it loads the page, otherwise it displays the value selected
}, []);
const setValue = (e) => {
setFilterChoice(e.target.value);
name(e.target.value);
};
return (
<div>
<select onChange={(e) => setValue(e)} defaultValue={defaultValue}>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
<option value="E">E</option>
</select>
</div>
);
};
const Parent = () => {
const [choice, setChoice] = useState(""); // <-- Leaving a blank state
return (
<div>
<h1>{choice === "" ? "NO VALUE" : choice}</h1>
<Child name={(choice) => setChoice(choice)} />
</div>
);
};
export default Parent;
Can you try the above solution, I have just set the default value of options in useEffect.
Related
Actually, I passed dropdown value in the URL, but how to show the selected value after the page refresh? please solve this issue. I want to try when user select any option then value show on url and after page refresh same selected value show .Thank you
import React, { useState, useEffect } from "react";
import { Link ,navigate} from "gatsby";
export default function IndexPage() {
const [data, setData] = useState("black");
const Vdata = [{
title:"black"
},
{
title:'red'
}]
const handleChange = (value) => {
setData(value);
navigate(`/?location=${value}`);
};
return (
<div className="grid place-items-center">
<select
value={data}
autocomplete="off"
name=""
id=""
className="border p-2 shadow-xl"
onChange={(event) => handleChange(event.target.value)}
>
{Vdata.map((i) => (
<option value={i.title} selected>
{i.title}
</option>
))}
</select>
<p>{window.location.href}</p>
</div>
);
}
// export default IndexPage
The Window localStorage object allows you to save key/value pairs in the browser.
detail info
Initiate data with localStorage stored info, if null default set "black"
const [data, setData] = JSON.parse(localStorage.getItem('title')) || "black";
Add useEffect function to store this selected value when set data.
const handleChange = (value) => {
setData(value);
navigate(`/?location=${value}`);
};
React.useEffect(() => {
localStorage.setItem('title', JSON.stringify(data));
}, [data]);
Finally, Vdata.map((i) => ( should add some condition to set selected attribute.
My home page has a drop down, I want to pass the selected value to the app.js file which has a header and a menu, I want that selected value to appear in the header which is on the app.js file, is that possible?
my home.js code:
const Home = () => {
const [ environment, setEvironment ] = useState([]);
const ddOnChange = async (e) => {
setEnvironment(e.target.value);
}
return (
<select className="dropdown main" onChange={ ddOnChange }>
<option value="0"></option>
<option value="NW">Northwest</option>
<option value="SE">South East</option
</select>
)
}
export default Home;
Is there a way to pass that environment value to the app.js file so it appears in the menu header on that page?
In your app.js define a state and pass it's setter to your home:
const [activeHeader, setActiveHeader] = React.useState();
return (
......
<Home setActiveHeader={setActiveHeader} />
......
)
Then in your Home.js, update the state using the setter:
const Home = ({setActiveHeader}) => {
const ddOnChange = async (e) => {
setActiveHeader(e.target.value);
}
return (
<select className="dropdown main" onChange={ ddOnChange }>
<option value="0"></option>
<option value="NW">Northwest</option>
<option value="SE">South East</option
</select>
)
}
export default Home;
There are 4 select Components with dependant dropdown menu.But when I select an option its not displaying in the input field although my 'selectedPlanets' state is updating just right.
Here is my code -
import React, { useState } from "react";
import "../css/Destination.css";
function Destination(props) {
const [selectedPlanets, SetselectedPlanets] = useState([
null,
null,
null,
null,
]);
const OnSelectPlanet = async (e, key) => {
const clonedSelectedPlanets = JSON.parse(JSON.stringify(selectedPlanets));
clonedSelectedPlanets[key] = e.target.value;
SetselectedPlanets(clonedSelectedPlanets);
};
const CustomSelectComponents = ({ value, options, OnSelect}) => {
return (
<select value={value} onChange={OnSelect}>
<option> -- Select a Planet -- </option>
{options.map((option) => {
return <option key = {option.name} value={option.name} >{option.name}</option>;
})}
</select>
);
};
const OptionsToRender = (Alloptions, AllselectedOptions, index) => {
console.log(AllselectedOptions);
const optionstoRender =
AllselectedOptions[index] != null
? Alloptions.filter(
(option) =>
!AllselectedOptions.some(
(selectedOption) =>
option && selectedOption && option.name === selectedOption
)
)
: Alloptions;
return optionstoRender;
};
return (
<>
<div className="Parent_Card">
{selectedPlanets.map((planet, index) => {
const options = OptionsToRender(props.planets, selectedPlanets, index);
return (
<>
{console.log(index)}
<CustomSelectComponents
value={
selectedPlanets[index] != null ? selectedPlanets[index] : ""
}
options={options}
OnSelect={(e) => OnSelectPlanet(e, index)}
key={index}
/>
</>
);
})}
</div>
</>
);
}
export default Destination;
I tried debugging it and figured that its maybe because of how and when my component is rendering.But I dont know why and hence not able to find the solution.
My expected result is when I am choosing an option it shows in the input field.
Your code could benefit from a few different approaches to building your 4 different select components:
the use of controlled components
(https://reactjs.org/docs/forms.html#controlled-components)
separating the state for each of the different selects
refactoring the <CustomSelectComponent /> to be a component that only accepts props
Here is an example of those approaches in practice that might provide some direction on getting these selects operating as expected!
import React, { useState } from 'react';
import '../css/Destination.css';
// custom select component
const CustomSelectComponent = ({ onChange, options, value }) => (
<select onChange={onChange} value={value}>
<option> -- Select a Planet -- </option>
{options.map(option => (
<option key={option.name} value={option.name}>
{option.name}
</option>
))}
</select>
);
const Destination = () => {
// mock props
const props = { planets: [{ name: 'Pluto' }, { name: 'Earth' }] };
// separated state
const [selectOneValue, setSelectOneValue] = useState('');
const [selectTwoValue, setSelectTwoValue] = useState('');
const [selectThreeValue, setSelectThreeValue] = useState('');
const [selectFourValue, setSelectFourValue] = useState('');
return (
<div className="Parent_Card">
{/* each custom select component is now controlled by it's own state */}
<CustomSelectComponent
onChange={e => setSelectOneValue(e.target.value)}
options={props.planets}
value={selectOneValue}
/>
<CustomSelectComponent
onChange={e => setSelectTwoValue(e.target.value)}
options={props.planets}
value={selectTwoValue}
/>
<CustomSelectComponent
onChange={e => setSelectThreeValue(e.target.value)}
options={props.planets}
value={selectThreeValue}
/>
<CustomSelectComponent
onChange={e => setSelectFourValue(e.target.value)}
options={props.planets}
value={selectFourValue}
/>
</div>
);
};
export default Destination;
Basically, I'm trying to test a component that have a select.
When trying to test the component, the test fails by returning the default value instead of the changed value.
But when I take the HTML of the rendered component (from screen.debug()) it works.
The component:
export function SelectFile({
fileList,
handleChange,
selected,
}) {
return (
<select
className="bg-slate-600 rounded w-auto"
onChange={onChange}
value={selected}
>
<option value="">Select an option</option>
<TodayOptions />
<AllOptions />
</select>
);
function AllOptions() {
return (
<>
{Object.entries(groups).map(([key, value]) => {
return (
<optgroup key={key} label={key.toLocaleUpperCase()}>
{[...value].sort(sortByDateFromLogs).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</optgroup>
);
})}
</>
);
}
function TodayOptions() {
const todayFiles = Object.values(groups)
.map((group) => {
const today = new Date().toLocaleDateString().replace(/\//g, '-');
return group.filter((file) => file.includes(today));
})
.flat();
if (todayFiles.length === 0) {
return null;
}
return (
<optgroup label="Today">
{todayFiles.map((item) => (
<option key={item}>{item}</option>
))}
</optgroup>
);
}
}
The original test:
it('should change option', () => {
render(
<SelectFile
fileList={fileList}
handleChange={handleChange}
selected=""
/>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(1);
expect(selectElement).toHaveValue(optionSelected); // returns "" (default value)
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
And the modified test with the rendered html:
it('should change option', () => {
render(
<div>
<div className="flex mr-10">
<h3 className="text-lg font-bold mr-4">Select a file</h3>
<select className="bg-slate-600 rounded w-auto">
<option value="">Select an option</option>
<optgroup label="ADONIS">
<option value="adonis-03-02-2022.json">
adonis-03-02-2022.json
</option>
<option value="adonis-02-02-2022.json">
adonis-02-02-2022.json
</option>
</optgroup>
<optgroup label="ERRORS">
<option value="http_errors-03-03-2022.log">
http_errors-03-03-2022.log
</option>
<option value="http_errors-04-02-2022.log">
http_errors-04-02-2022.log
</option>
</optgroup>
</select>
</div>
</div>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(selectElement).toHaveValue(optionSelected); // this returns the optionSelected value
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
Considering it works with the modified test, I can't make it why it doesn't on the original.
I've considered it was due to the optgroup, but it doesn't seems the case, so now I'm at a loss as to why.
Edit: the final version of the test:
it('should change option', () => {
const mockHandleChange = handleChange.mockImplementation(
(cb) => (e) => cb(e.target.value),
);
render(
<SelectWrapper fileList={fileList} handleChange={mockHandleChange} />,
);
const selectElement = screen.getByDisplayValue('Select an option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(2); // 1 for cb wrapper, 1 for select
expect(selectElement).toHaveValue(optionSelected);
});
});
const SelectWrapper = ({ handleChange, fileList }) => {
const [selected, setSelected] = useState('');
const mockHandleChange = handleChange(setSelected);
return (
<SelectFile
fileList={fileList}
handleChange={mockHandleChange}
selected={selected}
/>
);
};
I've created a wrapper to make it like you would use in another component, wrapped the mock function and now it changes the value and you have access to the mock.
Since in your test you are rendering only the Select (which is a controlled component : it receives from its parent the current value and a onChange callback), with a fixed selected props, you cannot expect the selected option to change when you trigger a change event on the select. You can only expect that the onChange callback has been called (like you do).
For this kind of component, you need to test that the selected props is respected (the selected option is the good one), and that the provided callback is called when the user chooses a new option (you ahve done this part).
You need to add a test with an existing option as selected props (not empty string), then check that the selected option is the right one. I suggest you use https://github.com/testing-library/jest-dom#tohavevalue from https://github.com/testing-library/jest-dom.
I've been working on this project for the last couple of hours and I was pretty sure that this final hour would be my last. No errors are appearing. My thinking is that when I pick a hero from the drop down, the page will update depending on my choice. I may have something that isn't firing that I'm not picking up on.
import React, {useEffect, useState} from 'react'
import axios from 'axios'
require("regenerator-runtime/runtime");
const App = () => {
const [hero, selectedHero] = useState(
'Select a Hero'
);
const handleChange = event => selectedHero(event.target.value);
return(
<HeroSelect heroSelect={hero} onChangeHeadline={handleChange} />
);
};
const HeroSelect = ({heroSelect, onChangeHeadline}) => {
const [data, setData] = useState({heroes: []});
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://api.opendota.com/api/heroStats',
);
setData({...data, heroes: result.data});
};
fetchData();
}, []);
return (
<div>
<h1>{heroSelect}</h1>
<select>
{data.heroes.map(item => (
<option key={item.id} value={heroSelect} onChange={onChangeHeadline} >
{item.localized_name}
</option>
))}
</select>
</div>
)
};
export default App
Define your onChange={onChangeHeadline} on Select tag not on option tag
<select onChange={onChangeHeadline}>
{data.heroes.map(item => (
<option key={item.id} value={item.localized_name}>
{item.localized_name}
</option>
))}
</select>
You should be firing your onChange event on the select tag itself.
<select onChange={onChangeHeadline} >
.....
.....
</select>
I reckon you didn't declare an onChange on the select.
Using This method:
<select id="lang" onChange={this.change} value={this.state.value}>
<option value="select">Select</option>
<option value="Java">Java</option>
<option value="C++">C++</option>
</select>