How to render the CloseIcon only when the image is inserted? - javascript

I am trying to create a form similar to google forms. Whenever a user inserts image I want to preview the image. Now, I have created a function updateImageLink to update the state once user inserts image. The function is below:
updateImageLink = (link, position) => {
let questions = [...this.state.questions];
let i = position.question;
if (position.option == null) {
questions[i].questionImage = link;
} else {
var j = position.option
questions[i].options[j].optionImage = link;
}
this.setState({ questions });
}
Suppose the user wants to delete the image so for that I have created a delete button and passed the function through onClick prop as follows:
<IconButton
style={{
marginLeft: "3px",
marginTop: "-15px",
zIndex: 999,
backgroundColor: "lightgrey",
color: "grey",
}}
size="small"
onClick={() => {
this.updateImageLink("", { question: i, option: null });
}}
>
<CloseIcon />
</IconButton>
Now, this code renders the CloseIcon even when there's no image. The problem is with binding the function. If I write:
onClick={this.updateImageLink("", { question: i, option: null })}
Then CloseIcon does not render and the state does not get updated as well.
The state of class is as follows:
this.state = {
questions: [
{
questionText: "Question 1",
questionImage:null,
options: [
{ optionText: "Option 1",
optionImage: null },
{ optionText: "Option 2",
optionImage: null },
{ optionText: "Option 3",
optionImage: null },
{ optionText: "Option 4",
optionImage: null }
],
correctAnswer: ""
}
],
openUploadImagePop: false,
imagePositionData: {
question: null,
option: null
}
}
So, how to render the CloseIcon only when the image is inserted?

I solved this question by updating the state as follows:
this.state = {
questions: [
{
questionText: "Question 1",
options: [
{ optionText: "Option 1" },
{ optionText: "Option 2" },
{ optionText: "Option 3" },
{ optionText: "Option 4" }
],
correctAnswer: ""
}
],
openUploadImagePop: false,
imagePositionData: {
question: null,
option: null
}
};
}
Here as there's no questionImage or optionImage field the unnecessary CloseIcon rendering problem is solved. Whenever, image is uploaded questionImage or optionImage gets added in the state through updateImageLink function.

Related

Select value doesnt change the first time I trigger onChange event when using setSate React

I have a set of select menus and I am trying to change a value when I select an option using onChange={updateValue} event. When I first select an option, the value is not being updated in the select menu.
It only changes the second time I try to choose an option. Not sure what I am doing wrong.
Edit: I did some more research (OnChange event using React JS for drop down) and I believe I need the value of the select to be updated as well, using setState. I cant figure out how to do it without having a variable for each value and set the state again.
let selectMenus = [
{
id: 'id1',
name: 'name1',
label: 'label1',
value: '0',
options: [
{
text: 'All ages',
value: '0',
},
{
text: '35 - 37 yrs',
value: '1',
},
],
buttonLabel: 'Refresh',
},
{
id: 'id2',
name: 'name2',
label: 'label2',
value: '1',
options: [
{
text: 'All ages',
value: '0',
},
{
text: '45 - 50 yrs',
value: '1',
},
],
buttonLabel: 'Refresh',
},
];
const [url, setUrl] = useState('http://localhost:5000/selectDropdowns1');
const updateValue = () => {
setUrl('http://localhost:5000/selectDropdowns2');
};
<form>
{selectMenus.map((select) => (
<div key={select.id} className='select-container'>
<label htmlFor={select.id}>{select.label}</label>
<select id={select.id} name={select.name} value={select.value} onChange={updateValue}>
{select.options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
<button>{select.buttonLabel}</button>
</div>
))}
</form>;
The problem is that when you provide onChange prop to select component it become a controlled component.
For more information: React Docs - Forms #controlled components
When you dealing with controlled components you must provide a value to it and when onChange triggerd it should update that value to work properly. Since you did not provide the full code, I imagine you have an array of select menus and options attached to it.
So in this case every select component should have own onChange method and own value to work properly. To achive this we should create another component for only Select Options. Like this;
function SelectComponent({ optionList, onSelected }) {
const [value, setValue] = useState();
const updateValue = ({ target }) => {
setValue(target.value);
if (onSelected) onSelected(target.value);
};
return (
<>
<label htmlFor={optionList.id}>{optionList.label}</label>
<select
id={optionList.id}
name={optionList.name}
value={value}
onChange={updateValue}
>
{optionList.options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
<button>{optionList.buttonLabel}</button>
</>
);
}
This component accepts to props; optionList and onSelected
optionList is the list of options to render
onSelected is a method that we call when user select and option
On main component, we should change the select section with our select component with props optionList and onSelected
return (
<div>
{selectMenus.map((select) => (
<div key={select.id} className="select-container">
<SelectComponent optionList={select} onSelected={updateValue} />
</div>
))}
</div>
);
So overall code is like this:
import { useState } from "react";
import { v4 as uuid } from "uuid";
export default function App() {
const [url, setUrl] = useState();
const updateValue = (value) => {
setUrl(value);
};
const selectMenus = [
{
id: 1,
label: "Menu 1",
name: "menu1",
buttonLabel: "Menu 1",
options: [
{
text: "option 1",
value: "option1"
},
{
text: "option 2",
value: "option2"
},
{
text: "option 3",
value: "option3"
}
]
},
{
id: 2,
label: "Menu 2",
name: "menu2",
buttonLabel: "Menu 2",
options: [
{
text: "option 1",
value: "option1"
},
{
text: "option 2",
value: "option2"
},
{
text: "option 3",
value: "option3"
}
]
},
{
id: 3,
label: "Menu 3",
name: "menu3",
buttonLabel: "Menu 3",
options: [
{
text: "option 1",
value: "option1"
},
{
text: "option 2",
value: "option2"
},
{
text: "option 3",
value: "option3"
}
]
}
];
return (
<div className="App">
<h1>URL Value: {url}</h1>
{selectMenus.map((select) => (
<div key={select.id} className="select-container">
<SelectComponent optionList={select} onSelected={updateValue} />
</div>
))}
</div>
);
}
function SelectComponent({ optionList, onSelected }) {
const [value, setValue] = useState();
const updateValue = ({ target }) => {
setValue(target.value);
if (onSelected) onSelected(target.value);
};
return (
<>
<label htmlFor={optionList.id}>{optionList.label}</label>
<select
id={optionList.id}
name={optionList.name}
value={value}
onChange={updateValue}
>
{optionList.options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
<button>{optionList.buttonLabel}</button>
</>
);
}
Working example is overhere codesandbox

how can i add object in array which is in object as property (react.js )

first off, sorry for the confusing title. I couldn't find a way to say this more clearly.
These days, I'm working on my own project and face a problem. If you can give me some
advice, it would be a huge help for me.
So, this is my State
const[state, setState] = useState({
externalEvents: [
{ title: "Art 1", color: "#0097a7", id: 34432, custom: "fdsfdsfds" },
{ title: "Art 2", color: "#f44336", id: 323232 },
{ title: "Art 3", color: "#f57f17", id: 1111 },
{ title: "Art 4", color: "#90a4ae", id: 432432 },
]
});
and this is my input tags and function
<form className="todoinput" onSubmit={addTodo} >
<input type="text" value={title} onChange={e=>setTitle(e.target.value)} placeholder="Add event" />
<input type="text" value={custom} onChange={e=>setCustom(e.target.value)} placeholder="detail" />
<select>
<option value={()=>{setColor('blue')}}>blue</option>
<option value={()=>{setColor('orange')}}>orange</option>
<option value={()=>{setColor('green')}}>green</option>
<option value={()=>{setColor('purple')}}>purple</option>
</select>
<button type="submit">Add todo</button>
</form>
const addTodo = (e) =>{
e.preventDefault();
setState([...state.externalEvents,{title:{title}, color: {color}, custom: {custom}}])
setTitle('')
setCustom('')
}
This is what I wanted to make: I type title,custom and select color in input and select tags. And I submit it, then function is going to add new object in externalEvents(array)
I used spread to do this, but somehow it didn't work and console says "TypeError: Cannot read property 'map' of undefined"
<div id="external-events">
{state.externalEvents.map((event) => (
<div
className="fc-event"
title={event.title}
data-id={event.id}
data-color={event.color}
data-custom={event.custom}
key={event.id}
style={{
backgroundColor: event.color,
borderColor: event.color,
cursor: "pointer"
}}
>{event.title}</div>
))}
</div>
this is a part where map() happen, I think the reason map couldn't read property is because
I failed to send proper property to externalEvents.
thanks for reading, and your help will be appreciated.
p.s I used FullCalendar library!
When you initialized your state, it was an object with a key externalEvents which is an array
useState({
externalEvents: [
{ title: "Art 1", color: "#0097a7", id: 34432, custom: "fdsfdsfds" },
{ title: "Art 2", color: "#f44336", id: 323232 },
{ title: "Art 3", color: "#f57f17", id: 1111 },
{ title: "Art 4", color: "#90a4ae", id: 432432 },
]
})
But when you update your state, it is an array
setState([...state.externalEvents,{title:{title}, color: {color}, custom: {custom}}])
So based on your initial state, setState should be as below
setState({
...state,
externalEvents: [
...state.externalEvents,
{title, color, custom}
]})
Do note that {title, color, custom} is probably what you want, instead of {title: {title}, xxxx
Your problem is probably inside of your addTodo function.
This function sets state to be an array. Following this, state.externalEvents no longer exists.
To test this, try console.log(state) after it has been set in addTodo function.
Based on your intent, here is a modification of your addTodo function that may solve your problem:
const addTodo = (e) =>{
e.preventDefault();
// Use previous state, and make sure to return an object with an 'externalEvents' key as the new state
setState((prevState) => {
const newEvent = {} // whatever your new event is
return { externalEvents: [...prevState.externalEvents, newEvent] }
})
setTitle('')
setCustom('')
}
Further improvement
Furthermore, you can make things more simple by directly having an externalEvents piece of state, to remove the need for a nested externalEvents property inside some other state object.
For example:
const [externalEvents, setExternalEvents] = useState([
{ title: "Art 1", color: "#0097a7", id: 34432, custom: "fdsfdsfds" },
{ title: "Art 2", color: "#f44336", id: 323232 },
{ title: "Art 3", color: "#f57f17", id: 1111 },
{ title: "Art 4", color: "#90a4ae", id: 432432 }
])
If you adopt this, you would need to update your addTodo function once again, specifically the state update step.
Your state update step will now look like this:
setExternalEvents((prevExternalEvents) => {
const newEvent = {} // whatever your new event is
return [...prevExternalEvents, newEvent]
})
See here for more:
Setting state based on the previous state: useState hook, setState function. Accessing previous state value
I think that the issue is that you defined state as an object, with the key externalEvents, which is an array. But when you are submitting the form, in the setState function, you are setting an array, not the original shape.
I recommend you to do it like this:
const [externalEvents, setExternalEvents] = useState([
{ title: "Art 1", color: "#0097a7", id: 34432, custom: "foo" },
{ title: "Art 2", color: "#f44336", id: 323232 },
{ title: "Art 3", color: "#f57f17", id: 1111 },
{ title: "Art 4", color: "#90a4ae", id: 432432 },
]);
And in the form:
const addTodo = (e) => {
...
setExternalEvents(prev => [...prev, {title, color, custom}])
...
}

Updating State Through props with hooks

I am trying to update the state of something on a click from a component by lifting up the state and passing it as a prop into the other component and trying to update it.
this is the App.js
function App() {
const [currentConfig, setCurrentConfig] = useState(0);
const availableConfigs = [
{ id: 1, name: "Config 1", number: 1, key: 1 },
{ id: 2, name: "Config 2", number: 2, key: 2 },
{ id: 3, name: "Config 3", key: 3 },
{ id: 4, name: "Config 4", key: 4 },
{ id: 5, name: "Config 5", key: 5 },
{ id: 6, name: "Config 6", key: 6 },
{ id: 7, name: "Config 7", key: 7 },
];
const [configs, setConfigs] = useState(availableConfigs);
//function undoConfigAnimation(currentConfig) {}
return (
<div>
<Tree
configs={configs}
animateConfigs={startConfigAnimation}
setConfig={setCurrentConfig}
currentConfig={currentConfig}
/>
<NavBar />
</div>
);
function startConfigAnimation(configClicked) {
console.log(currentConfig);
configs.forEach((config) => {
if (configClicked !== config.name) {
var elm = document.getElementById(config.name);
elm.style.transform = "translate(-200px)";
setTimeout(() => (elm.style.transform = "rotateZ(180deg)"), 1000);
}
});
}
}
export default App;
this is the component
function Tree(props) {
return (
<div class="treeContainer">
{props.configs.map((config) => {
return (
<div
id={config.name}
class="container1"
onClick={() => {
props.setConfig(config.name);
props.animateConfigs(config.name);
if (props.currentConfig !== config.name) {
props.setConfig.bind(config.name);
}
}}
>
<Configs configNumber={config.number} configName={config.name} />
</div>
);
})}
</div>
);
}
export default Tree;
currently, it does update the state, but it only updates it to the state before the click so an example output if the currentConfig === 0 would be as follows
click config 1
currentConfig = 0
click config 2
currentConfig = "config 1"
Since the setState is async, the console.log will always be one behind. This does not mean that the state is not updated, but only not displayed in the console or yet available in the function.
So the flow would be:
You dispatch the change.
You call startConfigAnimation, but it is still in sync, so that currentConfig is still the previous value.
The state is updated with the new value.
There are 2 ways to fix this:
Use a useEffect:
Listen to the currentConfig with a useEffect and trigger the animation, if the config changes.
React.useEffect(() => startConfigAnimation(currentConfig), [currentConfig])
You are already passing the new/updated config to startConfigAnimation so you could be using that.

React dynamic search bar not updating the list after selection of an item in shopping cart app

I am trying to write a shopping cart app which when we enter a value in search bar it should update the list and show item available based on value entered.
With the code I wrote, everything is working fine until I made a selection of an item which is not updating the view at first time and showing all the channels.(I think state not updating at first time, but doing at second time)
I am pretty new to react. I tried to add only necessary code here for solution.
state = { open: false, value: '', filteredSections: 'none', displaySections: [] };
filterFunction(e, someSections) {
const filteredSections = [];
this.setState({ value: e.target.value });
someSections.forEach((category) => {
const results = [];
const itemCopy = {...category};
category.arraysinside.forEach((item) => {
if (item.name.toLowerCase().includes(e.target.value.toLowerCase())) { results.push(item); }
});
itemCopy.arraysinside = results;
if (itemCopy.arraysinside.length > 0) { filteredSections.push(itemCopy); }
});
this.setState({ filteredSections: filteredSections });
return filteredSections;
}
updateFunction(displaySections) {
this.setState({ displaySections: displaySections });
}
onChange(opt) {
// makes item selected and changes color of item to know that item is selected
}
render() {
const someSections = customization.arraysinside.filter(c => c.has === 'channels');
let { displaySections, filteredSections } = this.state;
return (
<div>
<FormControl
name="search-items"
placeholder="Search items"
onChange={(e) => {
filteredSections = this.filterFunction(e, someSections);
this.setState({
displaySections: filteredSections.length > 0 ? displaySections = filteredSections : 'No',
value: this.state.value,
});
}}
/>
<div>
{displaySections.length > 0 ? displaySections.map(section =>
<someSections
onChange={(opt) => {
onChange(opt);
this.updateFunction(opt, displaySections);
}}
:
someSections.map(section =>
<someSections
onChange={opt => onChange(opt)}
/>)
}
</div>
</div>
);
}
}
JSON FORMAT WILL BE
{
"name": "product 1",
"has": "channels",
"arraysinside": [
{ "selected": false, "name": "Item 1"},
{ "selected": false, "name": "Item 2"},
{ "selected": false, "name": "Item 3"},
{ "selected": false, "name": "Item 4"},
],
},
{
"name": "product 2",
"has": "channels",
"arraysinside": [
{ "selected": false, "name": "Item 1"},
{ "selected": false, "name": "Item 2"},
{ "selected": false, "name": "Item 3"},
{ "selected": false, "name": "Item 4"},
],
},
displaySections should be updated every time I select the item and should change the view instantly, but with current code, It updates when I enter another value or update selection next time. PLease help, I been trying it for days with no improvement. I will be glad, If suggested a easier way than I wrote.
setState() is asynchronous, so try:
this.setState({ value: e.target.value }, () => { ... do stuff relying on value });

How to get a certain data in the second autocomplete input that depend on what typed in the first input in React.js?

Okay, so I don't know how to properly express my simple problem because of how simple it is, I guess.
Basically, I have an autocomplete done by me in my React project.. I have two inputs "Country" and "City". When I type a country my autocomplete works great giving me suggestions but now I have to make the same for my second input so it would give me a list of cities that depends on which country is typed in the "Country" input...
"United Kingdom" => "London, Birmingham, Bighton etc."
How can I do that? Thank you!
P.S. I already have all the lists of countries and cities, I just don't know how to make the second input to depend on an information in the first one.
Code here
Autocomplete.jsx
https://github.com/lembas-cracker/Weather-app/blob/master/src/Autocomplete.jsx
Form.jsx
https://github.com/lembas-cracker/Weather-app/blob/master/src/Form.jsx
P.S. I already have all the lists of countries and cities, I just don't know how to make the second input to depend on an information in the first one.
If you know which country the city belongs to (perhaps via a key in the city object), you could run a simple filter function to remove any cities that don't belong to that country.
this.state = {
selectedCountry: 'London',
};
const cities = [
{ name: "Toronto", country: "Canada" },
{ name: "London", country: "United Kingdom" }
];
const filteredCities = cities.filter(city => {
return city.country !== this.state.selectedCountry;
});
On your city input field make sure to create an onBlur function to will run the filter on your cities list once the user leaves that input field.
Made a quick example. Did you mean smth like this? Since you haven't provided any part of your source code, I used plain HTML select for the demo.
https://jsfiddle.net/arfeo/n5u2wwjg/204186/
class App extends React.Component {
constructor() {
super();
this.state = {
countryId: 1,
};
}
onCountryChange(countryId) {
this.setState({ countryId: parseInt(countryId) });
}
render() {
return (
<div>
<Input
key="countriesInput"
type="countries"
countryId={this.state.countryId}
onChange={(countryId) => this.onCountryChange(countryId)}
/>
<Input
key="citiesInput"
type="cities"
countryId={this.state.countryId}
/>
</div>
);
}
}
class Input extends React.Component {
constructor() {
super();
this.selectRef = null;
}
renderOptions() {
const countries = [
{
id: 1,
name: 'England',
},
{
id: 2,
name: 'Germany',
},
{
id: 3,
name: 'France',
},
];
const cities = [
{
countryId: 1,
cities: [
{
id: 1,
name: 'London',
},
{
id: 2,
name: 'Liverpool',
},
{
id: 3,
name: 'Salisbury'
}
],
},
{
countryId: 2,
cities: [
{
id: 4,
name: 'Berlin',
},
{
id: 5,
name: 'Frankfurt',
},
],
},
{
countryId: 3,
cities: [
{
id: 6,
name: 'Paris',
},
],
},
];
switch (this.props.type) {
case 'countries': {
return countries.map((country) => (
<option
key={country.id.toString()}
value={country.id}
>
{country.name}
</option>
));
}
case 'cities': {
const citiesMap = cities.filter((city) => city.countryId === this.props.countryId);
if (citiesMap && citiesMap[0]) {
const citiesList = citiesMap[0].cities;
if (citiesList) {
return citiesList.map((city) => (
<option
key={city.id.toString()}
value={city.id}
>
{city.name}
</option>
));
}
}
return null;
}
default: return null;
}
}
render() {
return (
<select name={this.props.type} ref={(ref) => this.selectRef = ref} onChange={() => this.props.onChange(this.selectRef.value)}>
{this.renderOptions()}
</select>
);
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
UPDATE
Make your Form component stateful.
Add a state property for countries in Form (let it be countryId).
Pass this property as a prop into the second Autocomplete component.
When the first Autocomplete changes, change the countryId of the Form.
I've done something similar which may help you.
The Object.keys(instutiontypes) you could use to have an array of countries, instead. Then inside of those values, you can have an array of objects. You could have the cities here, e.g. {value: "Manchester", "label: Manchester", phoneExt: "0114"}
const instutiontypes = {
Kindergarten: [
{ value: "PreK", label: "PreK" },
{ value: "K1", label: "K1" },
{ value: "K2", label: "K2" },
{ value: "K3", label: "K3" },
],
"Primary School": [
{ value: "Grade 1", label: "Grade 1" },
{ value: "Grade 2", label: "Grade 2" },
{ value: "Grade 3", label: "Grade 3" },
{ value: "Grade 4", label: "Grade 4" },
{ value: "Grade 5", label: "Grade 5" },
{ value: "Grade 6", label: "Grade 6" },
],
}
To have the options in my input, I use Object.keys(instutiontypes) to get ['Kindergarten','Primary School']
Then, to get the array of ages to give to my secondary dropdown, I have written this code:
const types = ['Selection1', 'Selection2']
const agesList = [];
for (let i = 0; i < types.length; i++) {
Object.values(institutionTypes[types[i]]).map(({ label }) =>
agesList.push(label)
);
}
This way, the ages dropdown list is dependent on the values passed to institutionTypes.
I'm using mui's <Autocomplete /> components to make them be search dropdowns, with the prop options for the arrays.

Categories