I'm pretty much ready to rip my hair out. So my final project in my Javascript class is an experimental thing with learning React.js, where you do a basic todo list. I got all that done and working, and I can have it add things properly. But my final hurdle is making it so that onclicking the printed paragraph from the button will cause them to give the printed paragraphs the strikethrough property, which can be undone by clicking on it again.
I've looked up everywhere, I've tried other examples from here, and nothing I can think of gets the strikethrough to take place. I tried a basic Javascript function that would do what I wanted if this was an HTML/non-react file, but it breaks the react page when I try to plop it in. So I spent a stupidly long time on a tutorial trying to figure out what to do, and I maybe figured out the step in the right direction? But I still can't get anything to happen and I don't know how to establish an onclick to this.
import React from 'react';
import './App.css';
class App extends React.Component {
setCurrentToDoItem = (toDoItem) => {
console.log("toDoItem", toDoItem);
this.setState({
currentToDoItem: toDoItem
});
};
saveToDoListItem = (toDoItem) => {
this.setState({
toDoList: [...this.state.toDoList,
toDoItem]
});
};
constructor(props) {
super(props);
this.state = {
currentToDoItem: null,
toDoList: [],
strikeThrough: []
};
}
render() {
return (
<div>
<h1>To Do List</h1>
<label>To Do Item: </label>
<input
onChange={(event) => this.setCurrentToDoItem(event.target.value)}>
</input>
<button onClick={() => this.saveToDoListItem(this.state.currentToDoItem)}>
<p>Add Item</p>
</button>
<p>{this.state.currentToDoItem}</p>
<div>
<p>To Do Items</p>
{
this.state.toDoList.map((item, index) => <p key={index}>{item} </p>)
}
</div>
</div>
);
}
}
export default App;
This is my App.js code. As you can see, everything else should work fine, but I have no clue how to add a strikethrough effect to what would result from the this.state.toDoList.map((item, index) => <p key={index}>{item} </p>) bit like I would with a function in normal javascript. How do I make the printed lines strikethrough via onclick, and then how do I undo that by clicking on it again? (I assume with a second onclick) I really just need to know how to get a working strikethrough with this, as everything else is pretty much working.
One of the most comfortable ways to do that is as advised in comments. A really quick way to implement this is to toggle class list. In the code bellow, I added a function crossLine which toggles class name "crossed-line" and adds event listener on mapped to-dos (in render function). Then in your App.css add a line
.crossed-line {
text-decoration: line-through;
}
Here's your edited component code.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentToDoItem: null,
toDoList: [],
strikeThrough: []
};
}
setCurrentToDoItem = toDoItem => {
this.setState({
currentToDoItem: toDoItem
});
};
saveToDoListItem = toDoItem => {
this.setState({
toDoList: [...this.state.toDoList, toDoItem]
});
};
crossLine = event => {
const element = event.target;
element.classList.toggle("crossed-line");
};
render() {
return (
<div>
<h1>To Do List</h1>
<label>To Do Item: </label>
<input
onChange={event =>
this.setCurrentToDoItem(event.target.value)
}
/>
<button
onClick={() =>
this.saveToDoListItem(this.state.currentToDoItem)
}
>
<p>Add Item</p>
</button>
<p>{this.state.currentToDoItem}</p>
<div>
<p>To Do Items</p>
{this.state.toDoList.map((item, index) => {
return (
<p onClick={this.crossLine} key={index}>
{item}{" "}
</p>
);
})}
</div>
</div>
);
}
}
As commented, you will have to keep a handle click and add class to add strikethrough using CSS.
For this I have updated your JSX to:
<p onClick={ () => this.handleClick(index) } className={ item.isComplete ? 'completed' : '' } key={index}>{item.value} </p>
and the signature of toDoItem from string to an object:
{
value: string;
isComplete: boolean
}
and based on this flag, I'm adding class.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentToDoItem: null,
toDoList: [],
strikeThrough: []
};
this.setCurrentToDoItem = this.setCurrentToDoItem.bind(this);
this.saveToDoListItem = this.saveToDoListItem.bind(this);
this.handleClick = this.handleClick.bind(this);
}
setCurrentToDoItem(toDoItem) {
this.setState({
currentToDoItem: toDoItem
});
}
saveToDoListItem(toDoItem) {
this.setState({
toDoList: [...this.state.toDoList, {
value: toDoItem,
isComplete: false
}]
});
}
handleClick(index) {
const {
toDoList
} = this.state;
toDoList[index].isComplete = !toDoList[index].isComplete;
this.setState({
toDoList
});
}
render() {
return (
<div>
<h1>To Do List</h1>
<label>To Do Item: </label>
<input
onChange={(event) => this.setCurrentToDoItem(event.target.value)}>
</input>
<button onClick={() => this.saveToDoListItem(this.state.currentToDoItem)}>
<p>Add Item</p>
</button>
<p>{this.state.currentToDoItem}</p>
<div>
<p>To Do Items</p>
{
this.state.toDoList.map((item, index) =>
<p onClick={ () => this.handleClick(index) } className={ item.isComplete ? 'completed' : '' } key={index}>{item.value} </p>)
}
</div>
</div>
);
}
}
ReactDOM.render( < App / > , document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.completed {
text-decoration: line-through;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Check out this solution https://codesandbox.io/s/crazy-kare-go2vf
I have modified your code to achieve the required functionality.
This code does exactly what you want.
Update : Created a TODO fiddler code using React Hooks for modern code approach.
const initialState = {
items: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn React", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
};
function appReducer(state, action) {
switch (action.type) {
case 'ITEM_STATUS_CHANGE':{
let affected = state.items.slice();
affected[action.index].done = !affected[action.index].done;
return Object.assign({}, state, { items: affected });
}
case 'ADD_ITEM_TO_LIST':{
let affected = state.items.slice();
affected.push({ text: action.data, done : false})
return Object.assign({}, state, { items: affected });
}
default:
throw new Error();
}
}
function TodoApp(props){
const [state, dispatch] = React.useReducer(appReducer, initialState);
return (
<div>
<h2>Todos:
<input type="text" id="todoTextItem"/>
<button
onClick={()=>{
dispatch({
type: 'ADD_ITEM_TO_LIST',
data: window.todoTextItem.value
})
}}
>Add Item</button>
</h2>
<ol>
{state.items.map((item, index) => (
<li key={index}>
<label>
<input
type="checkbox"
checked={item.done}
onChange={()=>{
dispatch({
type: 'ITEM_STATUS_CHANGE',
index: index,
})
}}
/>
<span className={item.done ? "done" : ""}>{item.text}</span>
</label>
</li>
))}
</ol>
</div>
);
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
and in CSS
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
Explanation:
Basically i am creating a list with done boolean flag which is false by default, which helps to identify if the TODO items added to the list is finished or not using reducers. With that logic class .done is toggled. You can change the code according to your need by segregating TODO list from done list items while setting state
This is a Unit testable code by creating Jest snapshot. Never to manipulate DOM directly, which will defeat the purpose of React's snapshot testing.
Old fiddle code using Class Component.
Use this to compare and learn modern hooks concepts from class based.
Related
I'm new to StackOverflow and looking forward to contributing back to the community!
My first question, I am trying to make some squares change color on the screeen, after an onClick event. I'm nearly there, but I keep getting an error when I try to update the state, which then should updates the color. Please could you let me know what I'm doing wrong?
App.js
import React from "react"
import boxes from "./boxes"
import Box from "./Box"
export default function App() {
const [squares, setSquares] = React.useState(boxes)
function changeOn() {
console.log(squares)//just checking I'm getting the full object
setSquares({
id: 1, on: false //this was previously [...prev], on: !squares.on
})
}
const squaresElement = squares.map(props => (
<Box key={props.id} on={props.on} onClick={changeOn} />
))
return (
<main>
{squaresElement}
</main>
)
}
Box.js
import React from "react"
export default function Box (props) {
const styles= props.on ? {backgroundColor: "#222222"} : {backgroundColor: "none"}
return (
<div className="box" style={styles} onClick={props.onClick}></div>
)
}
Boxes.js
export default [
{
id: 1,
on: true
},
{
id: 2,
on: false
},
{
id: 3,
on: true
},
{
id: 4,
on: true
},
{
id: 5,
on: false
},
{
id: 6,
on: false
},
]
I hope somebody can easily spot what's wrong here?
I was expecting to see the color of the top left box change to a different color, after a click.
There are two issues:
setSquares needs the whole array, so you need to give it a new squares array
The styling back to None does not work always. better to give it the white color again
Please find the codesandbox
export default function App() {
const [squares, setSquares] = React.useState(boxes);
function changeOn(id) {
setSquares(
squares.map((square) => {
return { ...square, on: id === square.id ? !square.on : square.on };
})
);
}
const squaresElement = squares.map((props) => (
<Box key={props.id} on={props.on} onClick={() => changeOn(props.id)} />
));
return <main>{squaresElement}</main>;
}
And in Box.js
const styles = props.on
? { backgroundColor: "#222222" }
: { backgroundColor: "#fff" };
You're calling setSquares and passing it a single object instead of an array.
On the next render squares.map(...) blows up because squares is the object, and the object doesn't have a map method.
// after this call squares is just this one object
setSquares({
id: 1, on: false
})
Here's a possible implementation that pushes the on/off responsibility into the box component itself.
// generates a list of items (faking your boxes.js)
const boxes = Array.from({length: 9}, (_, id) => ({ id }));
// container element to render the list
function Boxen ({ items }) {
return (
<div className="container">
{items.map((item, idx) => (
<Box item={item} key={idx} />
))}
</div>
)
}
// component for a single box that can toggle its own on/off state
function Box ({item}) {
const [active, setActive] = React.useState();
return (
<div onClick={() => setActive(!active)} className={active ? 'active' : ''}>{item.id}</div>
)
}
ReactDOM.render(<Boxen items={boxes}/>, document.getElementById('root'));
.container {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(3, 100px);
gap: 1em;
}
.container > * {
display: flex;
justify-content: center;
align-items: center;
background: skyblue;
}
.container > .active {
background: slateblue;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Testing out SvelteKit 1.0 with a basic todo app but I'm unable to make the text strikethrough conditionally. When the user clicks the checkbox on the left of the TodoItem the text should change style to become strikethrough. I logged out the values for completed and textDecoration whenever they change and so I'm fairly confident the data is being passed correctly, the style just isn't changing for some reason.
I tried following the example laid out in the docs here as closely as possible but it's still not working. Here is the code
+page.svelte (Home)
<script lang="ts">
import SearchBar from '$components/SearchBar.svelte';
import TodoItem from '$components/TodoItem.svelte';
let todos = [
{
id: 0,
text: 'first task',
completed: false
}
];
const addTodos = (taskText: string) => {
if (!taskText) return;
todos = [
...todos,
{
id: todos.length,
text: taskText,
completed: false
}
];
console.log(todos);
};
const checkTodo = (id: number) => {
todos[id].completed = !todos[id].completed;
};
</script>
<h1>to-do app</h1>
<SearchBar onAdd={(str) => addTodos(str)} />
{#each todos as todo}
<TodoItem
text={todo.text}
bind:completed={todo.completed}
onCheck={() => checkTodo(todo.id)}
onDelete={() => console.log(`Delete ` + todo.id)}
/>
{/each}
TodoItem.svelte
<script lang="ts">
export let text: string;
export let onDelete: () => void;
export let onCheck: () => void;
export let completed: boolean;
$: textDecoration = completed ? 'line-through' : 'none';
//console log when value changes
$: textDecoration, console.log(textDecoration);
$: completed, console.log(completed);
</script>
{#if text}
<div class="todoContainer">
<input
on:click={() => {
onCheck();
}}
id={`todo-checkbox-` + text}
type="checkbox"
/>
<div style:textDecoration style:textAlign="left">
{text}
</div>
<div on:click={onDelete} on:keypress={onDelete}>x</div>
</div>
{/if}
<style>
.todoContainer {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 50px;
border: 1px solid black;
border-radius: 5px;
margin: 10px 0;
}
</style>
Use dash instead of camelCase with the style:directive (an example seems to be missing in the docs and tutorial)
style:text-decoration={textDecoration}
I have an array of objects and when the user inputs a zipcode and click I want to loop through the array and if the users zipcode matches a zip code in the array output results if the zipcode dose not match output another result
I have attempted to use map and forEach on the array and each allow me to find the zipcode and provide out put Im running into trouble when the zipcodes do not match
class PricingTool extends Component {
state = {
zipCode: "",
finalZip: "",
routine: "",
rush: "",
sameDay: "",
city: "",
match: false
};
handleKeypress = e => {
const { zipCode } = this.state;
if (e.which === 13 && zipCode.length === 5) {
console.log("enterrerre");
this.zipcodeHandler();
}
};
zipcodeHandler = () => {
const { zipCode } = this.state;
this.setState({
finalZip: zipCode,
});
};
changeHandler = e => {
e.preventDefault();
if (e.target.value.length <= 5 && !isNaN(e.target.value)) {
this.setState({
[e.target.name]: e.target.value
});
}
};
render() {
const { finalZip, zipCode, match } = this.state;
let searchResult;
if(finalZip){
searchResult = zipCodes.map(cur => {
if (finalZip && finalZip == cur.zip) {
return (
<div className="pricing__searchResult">
<h1 className="pricing__searchResult-1">
We do serve in {cur.city}, Indiana
</h1>
<div className="pricing__searchResult-2">Same-Day</div>
<div className="pricing__searchResult-3">{cur.fees.sameDay}</div>
<div className="pricing__searchResult-4">Rush</div>
<div className="pricing__searchResult-5">{cur.fees.rush}</div>
<div className="pricing__searchResult-6">Routine</div>
<div className="pricing__searchResult-7">{cur.fees.routine}</div>
<div className="pricing__searchResult-8">
Please call us or email info#ccprocess.com to order
</div>
</div>
);
}
});
}
I would like it to find the user inputed zip code if it is in the data array and if it is not then render another message
Instead of using a Array map method, which would map each value of the array to something else (in your case, it would only map the zipcode found), you can (and should) use a better method for the job. The find method will find the first item that meet your criteria and return it, in your case, it could be finalZip && (finalZip == cur.zip). If no item is found for the expression given, undefined is returned.
render() {
const { finalZip, zipCode, match } = this.state;
let searchResult;
if(finalZip){
searchResult = zipCodes.find(cur => finalZip && (finalZip == cur.zip));
if(searchResult) {
// do something for when the zip code is found
}
else {
// do something when no zip code is found
}
}
}
Array find method documentation: MDN
Being not sure what all components you used or are currently in your application, I created a sample app which kind of simulates your requirement and possibly delivers the correct output.
So here, I had no idea of where the input will be taken from the user and how your onKeyPress listener would work, so I created another component that renders your data based on the input and check if that zipcode exist or not.
Like shown below, your ZipCode related information will be render by another component ZipTool and your input box is handled by PricingTool
Also, here's the jsfiddle if you want to play around with the code - https://jsfiddle.net/hf9Ly6o7/3/
I hope you find this useful.
class ZipTool extends React.Component {
constructor(props) {
super(props);
}
render() {
let { data } = this.props;
return (
<div>
<h1 className="pricing__searchResult-1">We do serve in { data.city }, Indiana</h1>
<div className="pricing__searchResult-2">Same-Day</div>
<div className="pricing__searchResult-3">{ data.fees.sameDay }</div>
<div className="pricing__searchResult-4">Rush</div>
<div className="pricing__searchResult-5">{ data.fees.rush }</div>
<div className="pricing__searchResult-6">Routine</div>
<div className="pricing__searchResult-7">{ data.fees.routine }</div>
</div>
);
}
}
class PricingTool extends React.Component {
constructor(props) {
super(props)
this.state = {
zipCodes: [
{
'zipcode': '12345',
'city': 'abc',
'fees': {
'sameDay': '43',
'rush': '90',
'routine': '20'
}
},
{
'zipcode': '54321',
'city': 'xyz',
'fees': {
'sameDay': '25',
'rush': '35',
'routine': '10'
}
}
],
zipCode: "",
finalZip: "",
routine: "",
rush: "",
sameDay: "",
city: "",
match: false
}
}
changeHandler(e) {
this.setState({
zipCode: e.target.value,
});
for (let code of this.state.zipCodes) {
if( code.zipcode === e.target.value ){
this.setState({match: true, finalZip: code})
break;
}
else {
this.setState({match: false, finalZip: null})
}
}
};
render() {
return (
<div>
<input type="text" onChange={this.changeHandler.bind(this)} onKeyPress={this.handleKeyPress} name={this.state.zipCode}></input>
<div className="pricing__searchResult">
{ this.state.finalZip ? <ZipTool data={this.state.finalZip} /> : <div>Not Found</div> }
<div className="pricing__searchResult-8">
Please call us or email info#ccprocess.com to order
</div>
</div>
</div>
)
}
}
ReactDOM.render(<PricingTool />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
What I want to do is to change the border on the input to red if the input value doesn't match any movie in the API call.
The user types in the input field and the call to the API shows the matching result.
If we don't have any result I would like the border on the input to be red.
But I can't see how I should make that happen.
The component Input is at the end of the code snippet.
CSS
.input-style {
padding: 7px;
border-radius: 5px;
border: 1px solid #cccccc;
font-family: Courier New, Courier, monospace;
transition: background-color 0.3s ease-in-out;
outline: none;
}
.input-style:focus {
border: 1px solid turquoise;
}
APP.js
class App extends Component {
constructor() {
super();
this.state = {
value: '',
items: [],
isLoading: false,
searchResult: null,
error: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// To handle search
handleChange(e) {
this.setState({ value: e.target.value });
}
handleSubmit(e) {
let searchResult = [];
for (var i = 0; i < this.state.items.length; i++) {
if (
this.state.items[i].name
.toLowerCase()
.indexOf(this.state.value.toLowerCase()) !== -1
) {
searchResult.push(this.state.items[i]);
} else {
console.log('No matches on your search, try again');
}
}
e.preventDefault();
// If we have something in the object searchResult
if (searchResult.length > 0) {
this.setState({
error: false,
value: '',
searchResult: searchResult,
});
} else {
this.setState({
error: true,
value: '',
searchResult: [],
});
}
}
// call to the API
componentDidMount() {
this.setState({ isLoading: !this.state.isLoading });
fetch('https://api.tvmaze.com/shows')
.then(response => response.json())
.then(data => {
this.setState({
items: data,
error: false,
});
this.setState({ isLoading: !this.state.isLoading });
})
.catch(console.error);
}
render() {
return (
<div className="App">
<Header />
<Loader isLoading={this.state.isLoading} />
<Input
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
value={this.state.value}
/>
{this.state.error ? (
<p className="errorMsg">No match on the search, try again!</p>
) : null}
<Search search={this.state.searchResult} />
</div>
);
}
}
export default App;
Input.js
function Input(props) {
return (
<div>
<form onSubmit={props.handleSubmit}>
<input
type="text"
className="input-style"
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
/>
<button id="bold" className="button-style" type="submit">
<i className="fa fa-search" />
</button>
</form>
</div>
);
}
export default Input;
You can pass the error into the Input component from the App
<Input
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
value={this.state.value}
error={this.state.error)
/>
and in your Input component:
<input
type="text"
className={props.error ? 'error-input-style' : 'input-style'}
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
/>
alternative you can also set an inline styling for the error condition:
<input
type="text"
className="input-style"
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
style={{ border: props.error ? '1px solid red' : '' }}
/>
You can easily do this. Have a flag, say resultFound, in the state of App.js, with an initial value of false. Then, in the function where you make the API call, update this resultFound depending on whether any result was obtained.
And, in the render(), before returning, assign inputClassName dynamically based on the this.state.resultFound, like so,
let inputClassName = '';
if (this.state.resultFound === false) {
inputClassName = 'input-style-error'; // new CSS class for errors
} else {
inputClassName = 'input-style';
}
Then, you can pass the inputClassName as a prop to Input and use it as <input>'s className, like so,
// in your App.js render() method's return
// ... existing components
<Input customStyle={inputClassName} ... />
// ...
<!-- in your Input.js render() method -->
<input type="text" className={props.customStyle} ... />
Whenever the API call happens, your state will change causing a re-render of the DOM (render() is called). During each call, we dynamically set the inputClassName based on the state's resultFound. And, accordingly, the right className will be applied to the <input>.
I will give bad names for classes and variables, just to make it super clear. You should use more generic ones.
The trick here is to give your Input a dynamic class via props, and if that expression turns true and the class is appended to the element, you can style it with css.
__CSS__
.input-style {
padding: 7px;
border-radius: 5px;
border: 1px solid #cccccc;
font-family: Courier New, Courier, monospace;
transition: background-color 0.3s ease-in-out;
outline: none;
}
.input-style:focus {
border: 1px solid turquoise;
}
.input-style.red-border {
border: 1px solid red;
}
__APP.js__
class App extends Component {
constructor() {
super();
this.state = {
value: '',
items: [],
isLoading: false,
searchResult: null,
error: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// To handle search
handleChange(e) {
this.setState({ value: e.target.value });
}
handleSubmit(e) {
let searchResult = [];
for (var i = 0; i < this.state.items.length; i++) {
if (
this.state.items[i].name
.toLowerCase()
.indexOf(this.state.value.toLowerCase()) !== -1
) {
searchResult.push(this.state.items[i]);
} else {
console.log('No matches on your search, try again');
}
}
e.preventDefault();
// If we have something in the object searchResult
if (searchResult.length > 0) {
this.setState({
error: false,
value: '',
searchResult: searchResult,
});
} else {
this.setState({
error: true,
value: '',
searchResult: [],
});
}
}
// call to the API
componentDidMount() {
this.setState({ isLoading: !this.state.isLoading });
fetch('https://api.tvmaze.com/shows')
.then(response => response.json())
.then(data => {
this.setState({
items: data,
error: false,
});
this.setState({ isLoading: !this.state.isLoading });
})
.catch(console.error);
}
render() {
return (
<div className="App">
<Header />
<Loader isLoading={this.state.isLoading} />
<Input
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
value={this.state.value}
showRedBorder={this.state.error === true} // or what ever your logic
/>
{this.state.error ? (
<p className="errorMsg">No match on the search, try again!</p>
) : null}
<Search search={this.state.searchResult} />
</div>
);
}
}
export default App;
__Input.js__
function Input(props) {
return (
<div>
<form onSubmit={props.handleSubmit}>
<input
type="text"
className={`input-style${props.showRedBorder ? ' red-border' : ''}`}
placeholder="Sök efter film.."
value={props.value}
onChange={props.handleChange}
/>
<button id="bold" className="button-style" type="submit">
<i className="fa fa-search" />
</button>
</form>
</div>
);
}
export default Input;
I'm trying to use data from an API (https://messi.hyyravintolat.fi/publicapi/restaurant/11/) in my React project. I was able to render each "date" from the API, but how do I render each "name" for each "date" in with this kind of .json (the ones immediately inside the "data" arrays in the API)? {item.data[name]} doesn't seem to be the way to do this. Here is the component class I'm using to get and render the data:
import React from 'react';
/*eslint-env jquery*/
class TestMenu extends React.Component {
constructor(props) {
super(props);
this.state = { food: [] };
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://messi.hyyravintolat.fi/publicapi/restaurant/11/')
.then(({ data }) => this.setState({ food: data }));
}
render() {
const foods = this.state.food.map((item, i) => (
<div>
<h1>{item.date}</h1>
<p>{item.data[name]}</p>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{foods}</div>
</div>
);
}
}
export default TestMenu;
After looking at the datasource, the JSON is in the following shape:
[
{
"date": some date,
"data": [
{
"name": some name,
...
},
{
"name": some other name,
...
}
...
]
},
{
"date": some other date,
"data": [ ... ]
},
...
]
So there are several names for a single date. You could render this like the following:
<h1>{item.date}</h1>
<ul>
{item.data.map((d, idx) => {
return <li key={idx}>{d.name}</li>
)}
</ul>
Note that I also use the indices when mapping the data in order to provide a unique key to React for each <li> element.
That's because you need a second loop as data (the nested one) is an array and not an object.
A simple example of looping with your data structure:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
list: []
};
}
componentDidMount() {
axios
.get("https://messi.hyyravintolat.fi/publicapi/restaurant/11/")
.then(({ data }) => {
this.setState({
list: data.data
});
});
}
render() {
const { list } = this.state;
return (
<div>
<ul>
{
list && list.map((obj) => {
return (
<li className="item">
<div className="date">
<span>Date: </span> <span>{obj.date}</span>
</div>
<div className="names">
{
obj.data.map((obj2) => {
return (
<div className="name">
<span>Name: </span> <span>{obj2.name}</span>
</div>
)
})
}
</div>
</li>
)
})
}
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.item {
list-style: none;
border: 1px solid #ccc;
padding: 10px;
}
.date {
font-weight: bold;
}
.names {
text-indent: 15px;
}
.name{
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>